package vn.mahjonggame.game;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Timer;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.log4j.Appender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;

import com.smartfoxserver.v2.entities.Room;
import com.smartfoxserver.v2.entities.SFSUser;
import com.smartfoxserver.v2.entities.User;
import com.smartfoxserver.v2.entities.data.ISFSArray;
import com.smartfoxserver.v2.entities.data.ISFSObject;
import com.smartfoxserver.v2.entities.data.SFSArray;
import com.smartfoxserver.v2.entities.data.SFSObject;
import com.smartfoxserver.v2.extensions.ExtensionLogLevel;

import vn.mahjonggame.game.request.CallChiHandler;
import vn.mahjonggame.game.request.CallPonHandler;
import vn.mahjonggame.game.request.CallRonHandler;
import vn.mahjonggame.game.request.ThrowTileHandler;
import vn.mahjonggame.metadata.GameActionType;
import vn.mahjonggame.metadata.MObject;
import vn.mahjonggame.metadata.MahjongCache;
import vn.mahjonggame.metadata.Response;
import vn.mahjonggame.metadata.ResponseCode;
import vn.mahjonggame.metadata.RoomVarName;
import vn.mahjonggame.metadata.Tile;
import vn.mahjonggame.metadata.TileSet;
import vn.mahjonggame.metadata.TileType;
import vn.mahjonggame.metadata.Player;
import vn.mahjonggame.metadata.UserVarName;
import vn.mahjonggame.metadata.Yaku;
import vn.mahjonggame.metadata.Yakuman;
import vn.mahjonggame.platform.PlatformClient;
import vn.mahjonggame.platform.PlatformConfig;
import vn.mahjonggame.platform.ServerMode;
import vn.mahjonggame.util.Common;
import vn.mahjonggame.util.MahjongUtil;
import vn.mahjonggame.util.RandomString;
import vn.mahjonggame.util.TileComparator;

public class Mahjong implements MahjongUtil {
	public static List<Tile> tiles = new ArrayList<Tile>();
	public static List<Tile> fullTiles = new ArrayList<Tile>();
	
	public static final int NUM_OF_TILE = 34;	
	public static final int UNKNOWN_TILE = 34;
	public static final int NUM_OF_PLAYER = 4;
	
	/*
	 * The number of tile on the player wall (for 1 layer)
	 */
	public static final int TILE_PER_WALL = 17;
	
	/*
	 * The Index of the DORA point on the death wall
	 */
	public static final int DORA_POINT_INDEX = 3;
	
	/*
	 * Create the whole tile list once at start time
	 */
	
	/*
	 * The size of death wall
	 */
	public static final int DEATH_WALL_SIZE = 7;

	/*
	 * The number of times to deal tile
	 */
	public static final int TILE_DEAL_TIMES = 4;
	
	/*
	 * The number of tile need to deal
	 */
	public static final int NUM_TILE_TO_DEAL = 52;

	static {
		int id = NUM_OF_TILE - 7;
		
		for(int i = 0; i < id; i++){
			if(i < 9){
				tiles.add(new Tile(i, TileType.MAN, i + 1));
			}
			else if(i < 18){
				tiles.add(new Tile(i, TileType.PIN, i - 9 + 1 ));
			}
			else {
				tiles.add(new Tile(i, TileType.SOU, i - 18 + 1));
			}					
		}
		
		tiles.add(new Tile(id, TileType.TON));
		
		id++;		
		tiles.add(new Tile(id, TileType.NAN));
		
		id++;
		tiles.add(new Tile(id, TileType.SHA));
		
		id++;
		tiles.add(new Tile(id, TileType.PEI));
		
		id++;		
		tiles.add(new Tile(id, TileType.HAKU));
		
		id++;
		tiles.add(new Tile(id, TileType.HASTU));
		
		id++;
		tiles.add(new Tile(id, TileType.CHUN));
						
		for(int i = 0; i < 4; i++) {			
			fullTiles.addAll(tiles);			
		}
	}
	
	private List<Player> players = new ArrayList<Player>();
	
	private Logger logger = null;
	
	private Room room;
	
	/*
	 * List of the tile on the death wall (2 layer)
	 */
	private List<List<Tile>> deathWall = new ArrayList<List<Tile>>();
	
	/*
	 * List of the tile on the wall (2 layer)
	 */
	private List<List<Tile>> wall = new ArrayList<List<Tile>>();
	
	/*
	 * List of the tile on the wall (1 layer)
	 */
	private List<Tile> doras = new ArrayList<Tile>();
	
	/*
	 * List of the tile which was picked from death wall
	 */
	private List<Tile> pickedDeathTile = new ArrayList<Tile>();
	
	
	/*
	 * The first DORA tile id
	 */
	
	private int firstDoraTileId;
		
	/*
	 * Pointing Player: Start to pick the tile from this Player wall
	 */
	private Player pointingPlayer = null;
	
	/*
	 * Thrown player
	 */
	private Player thrownPlayer = null;
	
	/*
	 * List of Player who can call RON
	 */
	private List<Player> canCallRonPlayers = new ArrayList<Player>();	
	
	/*
	 * The number of player that called RON
	 */
	private int numRonPlayer = 0;
	
	/*
	 * The number of tile on the wall
	 */
	private int numTileOnWall = 136 - NUM_TILE_TO_DEAL - (DEATH_WALL_SIZE * 2);
	
	/*
	 * This value is reset when the Riichi player win
	 */
	private Map<Integer, Boolean> numKCPCallDuringRiichiMap = new Hashtable<Integer, Boolean>();
	
	/*
	 * The player is being in turn
	 */
	private Player playerInTurn;
	
	private int dice1;
	private int dice2;
	
	/*
	 * TODO Remove
	 */
	private int counter = 0;	
	/*
	 * Remove
	 */
	
	private int counterTimer = 0;
		
	private boolean hasCalledFinishAPI = false;
	
	private boolean hasPlayerCalledRon = false;	
		
	/*
	 * List of the player that called RIICHI
	 */
	private List<Player> calledRiichiPlayers = new ArrayList<Player>();
	
	/*
	 * List of the player that called RON
	 */
	private List<Player> calledRonPlayers = new ArrayList<Player>();
	
	/*
	 * List of player in Tenpai mode
	 */
	private List<Player> tenpaiPlayers = new ArrayList<Player>();
	
	/*
	 * List of player that call KAN
	 */
	private List<Player> calledKanPlayers = new ArrayList<Player>();
	
	/*
	 * Player which can call CHI
	 */
	private Player canCallChiPlayer = null;
	
	/*
	 * Player which is calling CHI
	 */
	private Player calingChiPlayer = null;
	
	/*
	 * Player which can call PON
	 */
	private Player canCallPonPlayer = null;
	
	/*
	 * Player that is calling PON
	 */
	private Player callingPonPlayer = null;
	
	/*
	 * Player that is calling OPEN KAN
	 */
	private Player canCallOpenKanPlayer = null;
	
	/*
	 * Player that is calling OPEN KAN
	 */
	private Player callingOpenKanPlayer = null;
	
	/*
	 * Player that is waiting for calling pick tile
	 */
	private Player callingPickPlayer = null;
	
	/*
	 * Flag is used to determine if one player has picked tile
	 */
	private boolean hasPickedTile = false;
	
	/*
	 * List of tile is thrown
	 */
	private List<Tile> thrownTiles = new ArrayList<Tile>();
	
	/*
	 * Its values can be PON/CHI/KAN/TSUMO/RIICHI/RON
	 */
	private String skipType = null;
	
	private boolean isDebug;
	
	private Map mahjongCache = new ConcurrentHashMap();
	
	public Tile buildTile(int tileId){
		int tileIndex = 0;
		TileType tileType = null;
							
		if (tileId >= 0 && tileId < 9){
			tileIndex = tileId + 1;
			tileType = TileType.MAN;
		}
		else if (tileId >= 9 && tileId < 18){
			tileIndex = tileId - 9 + 1;
			tileType = TileType.PIN;
		}
		else if (tileId >= 18 && tileId < 27){
			tileIndex = tileId -18 + 1;
			tileType = TileType.SOU;
		}
		
		switch(tileId){
			case 27:						
				tileType = TileType.TON;						
				break;
			case 28:						
				tileType = TileType.NAN;						
				break;
			case 29:						
				tileType = TileType.SHA;						
				break;
			case 30:						
				tileType = TileType.PEI;						
				break;
			case 31:						
				tileType = TileType.HAKU;						
				break;
			case 32:
				tileType = TileType.HASTU;						
				break;
			case 33:
				tileType = TileType.CHUN;						
				break;					
		}
		
		return new Tile(tileId, tileType, tileIndex);
	}
		
	public void dealTile(){
		
		String server_mode = room.getVariable(PlatformConfig.SERVER_MODE).getStringValue();
		
		/*
		 * Shuffle tile
		 */
		List<Tile> shuffledTiles = new ArrayList<Tile>();
		shuffledTiles.addAll(fullTiles);
		Collections.shuffle(shuffledTiles);
				
		int tilePerPlayer = TILE_PER_WALL * 2;
		int count = 0;
					
		for(Iterator<Player> iplayers = players.iterator(); iplayers.hasNext();){
			Player iplayer = (Player)iplayers.next();
			
			List<Tile> wall1 = new ArrayList<Tile>();
			wall1.addAll(shuffledTiles.subList(count, TILE_PER_WALL + count));
			
			List<Tile> wall2 = new ArrayList<Tile>();
			wall2.addAll(shuffledTiles.subList(TILE_PER_WALL + count, tilePerPlayer + count));
			
			for(int i = 0; i < TILE_PER_WALL; i++){
				List<Tile> tiles = new ArrayList<Tile>();
				tiles.add(wall1.get(i));
				tiles.add(wall2.get(i));
				iplayer.getWall().add(tiles);
			}
			
			count += tilePerPlayer;			
		}
		
		/*
		 * Roll the dice
		 */
		this.dice1 = rollDice();
		this.dice2 = rollDice();		
		int dice = this.getDice1() + this.getDice2();
		
		int startTilePosition = TILE_PER_WALL - dice;			
		int startPlayerPosition = dice % 4;
				
		Player startPlayer = this.getPlayerByPosition(startPlayerPosition);
		Player nextPlayer = this.getNextPlayer(startPlayer);
		
		List<List<Tile>> startPlayerWall = startPlayer.getWall();
		List<List<Tile>> nextPlayerWall = nextPlayer.getWall();
		
		/*
		 * The player wall where the death wall is located
		 */
		startPlayer.setContributeToDeathWall(true);		
				
				
		/*
		 * Get the list of tile to deal
		 */
		List<List<Tile>> tileToDeal = new ArrayList<List<Tile>>();
		
		for(int i = startTilePosition - 1; i >= 0; i--){
			tileToDeal.add(startPlayerWall.remove(i));
		}
				
		Player player = startPlayer;
		boolean isDone = false;
		
		for(int i = 0; i < this.getPlayers().size() && !isDone; i++){
			Player previousPlayer = this.getPreviousPlayer(player);			
						
			for(int j = previousPlayer.getWall().size() - 1; j >= 0; j--){
				tileToDeal.add(previousPlayer.getWall().remove(j));				
				if(tileToDeal.size() == NUM_TILE_TO_DEAL/2) {
					
					if(previousPlayer.getWall().size() == 0){
						previousPlayer = this.getPreviousPlayer(previousPlayer);
					}
					
					previousPlayer.setPointing(true);
					this.setPointingPlayer(previousPlayer);
					isDone = true;
					break;
				}
			}
			
			player = previousPlayer;
		}
		
		/*
		 * Calculate the DORA tile
		 */
		int remainWallSize = startPlayerWall.size();
		if(remainWallSize < DEATH_WALL_SIZE){
			nextPlayer.setContributeToDeathWall(true);
		}
		
		Tile firstDora = null;
		if(remainWallSize < 3){				
			firstDora = nextPlayer.getWall().get(3 - remainWallSize - 1).get(0);				
		}
		else {
			firstDora = startPlayer.getWall().get(3 - 1).get(0);				
		}
		
		this.getDoras().add(firstDora);
		this.setFirstDoraTileId(firstDora.getId());		
				
		/*
		 * Deal tile
		 */
		for(int i = 0; i < TILE_DEAL_TIMES; i++){
			
			if(i == TILE_DEAL_TIMES - 1){
				this.getPlayers().get(0).getLeftTiles().add(tileToDeal.get(0).get(0));								
				this.getPlayers().get(1).getLeftTiles().add(tileToDeal.get(0).get(1));
				tileToDeal.remove(0);								
				this.getPlayers().get(2).getLeftTiles().add(tileToDeal.get(0).get(0));							
				this.getPlayers().get(3).getLeftTiles().add(tileToDeal.get(0).get(1));
				tileToDeal.remove(0);				
			}
			else{
				for(Iterator<Player> iplayers = this.getPlayers().iterator(); iplayers.hasNext();){
					Player iPlayer = (Player)iplayers.next();
					iPlayer.getLeftTiles().addAll(tileToDeal.remove(0));
					iPlayer.getLeftTiles().addAll(tileToDeal.remove(0));																	
				}
			}
		}
		
		if(server_mode.equalsIgnoreCase(ServerMode.DEBUG.toString())){
			
			List leftTilesList = new ArrayList();
			leftTilesList.add(room.getVariable(RoomVarName.player1.toString()).getStringValue());
			leftTilesList.add(room.getVariable(RoomVarName.player2.toString()).getStringValue());
			leftTilesList.add(room.getVariable(RoomVarName.player3.toString()).getStringValue());
			leftTilesList.add(room.getVariable(RoomVarName.player4.toString()).getStringValue());
			
			List pickTilesList = new ArrayList();
			pickTilesList.add(room.getVariable(RoomVarName.pick1.toString()).getStringValue());
			pickTilesList.add(room.getVariable(RoomVarName.pick2.toString()).getStringValue());
			pickTilesList.add(room.getVariable(RoomVarName.pick3.toString()).getStringValue());
			pickTilesList.add(room.getVariable(RoomVarName.pick4.toString()).getStringValue());
			
			int i = 0;			
			for(Iterator iPlayers = players.iterator(); iPlayers.hasNext(); ){
				Player iPlayer = (Player)iPlayers.next();
				logger.info(iPlayer.toString() + " " + leftTilesList.get(i));
				
				StringTokenizer st = new StringTokenizer(String.valueOf(leftTilesList.get(i)), " ");
				while (st.hasMoreElements()) {
					int tileId = Integer.valueOf(String.valueOf(st.nextElement()));
					
					Tile tile = buildTile(tileId);
					iPlayer.getLeftTiles().remove(0);
					iPlayer.getLeftTiles().add(tile);
				}
				
				st = new StringTokenizer(String.valueOf(pickTilesList.get(i)), " ");
				while (st.hasMoreElements()) {
					int tileId = Integer.valueOf(String.valueOf(st.nextElement()));
					Tile tile = buildTile(tileId);
					iPlayer.getWillPickTiles().add(tile);
				}
				
				i++;
			}			
		}
					
	}
	
	public boolean isCorrectedMahjong(){		
		int fullTilesSize = fullTiles.size();		
		if(fullTilesSize != 136) return false;
		
		for(int i = 0; i < fullTilesSize; i++){
			
		}
		
		return true;
	}
	
	public Tile pickTile(){
		Player pointingPlayer = this.getPointingPlayer();
		List<List<Tile>> pointingPlayerWall = pointingPlayer.getWall();
		
		int wallSize = pointingPlayerWall.size();
		
		if(wallSize == 0){
			Player nextPointingPlayer = this.getPreviousPlayer(pointingPlayer);			
			this.setPointingPlayer(nextPointingPlayer);
			pointingPlayerWall = nextPointingPlayer.getWall();
			wallSize = pointingPlayerWall.size();
		}
		
		Tile tile = pointingPlayerWall.get(wallSize - 1).remove(0);
		
		if(pointingPlayerWall.get(wallSize - 1).size() == 0){
			pointingPlayerWall.remove(wallSize - 1);			
		}
		
		return tile;
	}
	
	public Tile pickTileOnDeathWall(){
		for(Iterator<Player> iPlayers = this.getPlayers().iterator(); iPlayers.hasNext();){
			Player iPlayer = iPlayers.next();			
			if(iPlayer.isContributeToDeathWall()){
				Tile tile = iPlayer.getWall().get(0).remove(0);
				
				if(iPlayer.getWall().get(0).size() == 0){
					iPlayer.getWall().remove(0);
				}
				
				if(iPlayer.getWall().size() == 0){
					iPlayer.setContributeToDeathWall(false);
					this.getNextPlayer(iPlayer).setContributeToDeathWall(true);				
				}				
				return tile;
			}
		}
		
		return null;
	}
	
	public Tile pickTheNextDora() {
		for(int i = 0; i < this.getPlayers().size(); i++){
			Player iPlayer = this.getPlayers().get(i);			
			
			if(iPlayer.isContributeToDeathWall()) {
				int numPickedDeathTile = this.getPickedDeathTile().size();
				int numDora = this.getDoras().size();
				int index = 3 - 1 + numDora;				
				
				if(numPickedDeathTile > 1 && numPickedDeathTile < 4) {
					index = index - 1;
				}
				else if (numPickedDeathTile == 4) {
					index = index - 2;
				}
				
				int wallSize = iPlayer.getWall().size();
				
				Tile tile = null;
				
				if(index > wallSize - 1){
					index = index - wallSize;
					Player nextPlayer = this.getNextPlayer(iPlayer);
					tile = nextPlayer.getWall().get(index).get(0);
				}
				else {
					tile = iPlayer.getWall().get(index).get(0);
				}										
				return tile;
			}
		}
		
		return null;
	}
	
	/*
	 * Check if the deal chess is bad
	 */	
	public synchronized boolean isBadMahjong(){		
				
		for(Iterator<Player> iPlayers = this.getPlayers().iterator(); iPlayers.hasNext(); ){
			Player iPlayer = iPlayers.next();			
			if(iPlayer.isBadTileSet()){
				return true;
			}
		}
		
		return false;
	}	
	
	public Player getLastPlayer(){
		int playerPosition = this.getThrownPlayer().getPlayerPosition();
		if(playerPosition == 0){
			playerPosition = this.getPlayers().size() - 1;
			return this.getPlayerByPosition(playerPosition);
		}
		else{
			playerPosition--;
			return this.getPlayerByPosition(playerPosition);
		}
	}
	
	public Player getNextPlayer(Player player){
		int playerPosition = player.getPlayerPosition();
		
		if(playerPosition == this.getPlayers().size() - 1){
			return this.getPlayerByPosition(0);
		}
		else {
			playerPosition++;
			return this.getPlayerByPosition(playerPosition);
		}
	}
	
	public Player getNextCanCallRonPlayer(Player player){
		
		int i = 0;
		while(i < 3){
			Player nextPlayer = this.getNextPlayer(player);
			if(this.getCanCallRonPlayers().contains(nextPlayer)){
				return nextPlayer;
			}
			i++;
		}
		
		return null;
	}
	
	public Player getPreviousPlayer(Player player){
		int playerPosition = player.getPlayerPosition();
		
		if(playerPosition == 0){
			return this.getPlayerByPosition(this.getPlayers().size() - 1);
		}
		else {
			playerPosition--;
			return this.getPlayerByPosition(playerPosition);
		}
	}
	
	public Player getPlayerByPosition(int position){
		for(Iterator<Player> iPlayers = this.getPlayers().iterator(); iPlayers.hasNext();){
			Player iPlayer = iPlayers.next();
			if(iPlayer.getPlayerPosition() == position){
				return iPlayer;
			}
		}		
		return null;
	}
	
	public List<Integer> getCallRonUserIds(List<User> users){
		List<Integer> userIds = new ArrayList<Integer>();
		
		for(Iterator<Player> iPlayers = this.getPlayers().iterator(); iPlayers.hasNext();){
			Player iPlayer = iPlayers.next();			
			if(iPlayer.isRon()){
				User user = getUserByPlayerId(iPlayer.getPlayerId(), users);
				userIds.add(user.getId());
			}
		}
		
		return userIds;
	}
	
	public Tile getTileById(int tileId){
		Tile tile = tiles.get(tileId);
		return new Tile(tile.getId(), tile.getType(), tile.getIndex());
	}	

	public List<Player> getPlayers() {
		return players;
	}

	public void setPlayers(List<Player> players) {
		this.players = players;
	}
	
	public List<List<Tile>> getDeathWall() {
		return deathWall;
	}

	public void setDeathWall(List<List<Tile>> deathWall) {
		this.deathWall = deathWall;
	}

	public List<List<Tile>> getWall() {
		return wall;
	}

	public void setWall(List<List<Tile>> wall) {
		this.wall = wall;
	}
	
	public List<Player> getOnlinePlayers(){
		List<Player> players = new ArrayList<Player>();
		for(Iterator<Player> iPlayers = this.getPlayers().iterator(); iPlayers.hasNext();){
			Player iPlayer = iPlayers.next();
			if(!iPlayer.isDisconnect()){
				players.add(iPlayer);
			}			
		}
		return players;
	}
	
	public Player getPlayerById(int playerId){
		
		for(Iterator<Player> iPlayers = this.getPlayers().iterator(); iPlayers.hasNext();){
			Player iPlayer = iPlayers.next();
			if(iPlayer.getPlayerId() == playerId){
				return iPlayer;
			}
		}
		
		return null;
	}
	
	public List<Tile> getDoras(){		
		return doras;
	}
	
	public boolean canBeRemove(){
		for(Iterator<Player> iPlayers = this.players.iterator(); iPlayers.hasNext();){
			Player player = (Player)iPlayers.next();
			if(!player.isDisconnect()) return false;
		}
		return true;
	}
	
	public boolean canRon(){
		
		int numConntectedPlayer = 0;
		
		for(Iterator<Player> iPlayers = players.iterator(); iPlayers.hasNext();){
			Player player = (Player)iPlayers.next();
			if(!player.isDisconnect()) {
				numConntectedPlayer += 1;
			}
		}		
		
		if(numConntectedPlayer == 1) return true;
		
		return false;
	}
	
	public synchronized void calculateHanScore(Player player, Tile tile){
		
		player.checkYakus(tile);
		
		int hanScore = 0;
		
		List<String> yakuList = player.getYakuList();
		List<String> yakumanList = player.getYakumanList();
		List<String> otherYakumanList = player.getOtherYakumanList();
		
		yakuList.clear();
		yakumanList.clear();
		otherYakumanList.clear();
		
		/*
		 * YAKU
		 */
		
		/*
		 * +1 HAN Menzen Tsumo 
		 */
		if(player.isTsumo() && player.isMenzenTsumo()){
			hanScore++;
			yakuList.add(Yaku.MenzenTsumo.toString());
		}
		
		/*
		 * +1 HAN Riichi
		 */
		if(player.isRiichi()){
			hanScore++;
			yakuList.add(Yaku.Riichi.toString());
		}
		
		/*
		 * +1 HAN Ippatsu
		 */
		if(player.isRiichiWaiting()){
			
			int counter1 = 0;			
			int counter2 = 0;
			
			/*
			 * Only round 1
			 */
			for(Iterator<Player> iPlayers = this.getPlayers().iterator(); iPlayers.hasNext();){
				Player iPlayer = iPlayers.next();
				
				if(iPlayer.getThrownTiles().size() == 1){
					counter1++;
				}
				
				if(iPlayer.getRightTiles().size() == 0){
					counter2++;
				}
			}
			
			if(counter1 == 4 && counter2 == 4){
				hanScore++;
				yakuList.add(Yaku.Ippatsu.toString());
			}					
		}
		
		/*
		 * +1 HAN Tanyao
		 */
		if(player.isTanyao()){
			hanScore++;
			yakuList.add(Yaku.Tanyao.toString());
		}
		
		/*
		 * +1 HAN Pinfu
		 */
		if(player.isPinfu()){
			hanScore++;
			yakuList.add(Yaku.Pinfu.toString());
		}
		
		/*
		 * +1 HAN Fanpai
		 */
		if(player.isFanpai()){
			hanScore++;
			yakuList.add(Yaku.Fanpai.toString());
		}
		
		/*
		 * +1 HAN Rinshan Kaihou
		 */
		if(player.isRinshanKaihou()){
			hanScore++;
			yakuList.add(Yaku.RinshanKaihou.toString());
		}
		
		/*
		 * +1 HAN Chankan
		 * TODO
		 */
		if(player.isChankan()){
			hanScore++;
			yakuList.add(Yaku.Chankan.toString());
		}
		
		/*
		 * +1 HAN Haitei Raoyue
		 */
		if(player.isHaiteiRaoyue() && player.isTsumo()){
			if(this.getPointingPlayer().getWall().size() == 0){
				hanScore++;
				yakuList.add(Yaku.HaiteiRaoyue.toString());
			}
		}
		
		/*
		 * +1 HAN Haitei Raoyui
		 */
		if(player.isHaiteiRaoyui() && player.isRon()){
			if(this.getPointingPlayer().getWall().size() == 0){
				hanScore++;
				yakuList.add(Yaku.HaiteiRaoyui.toString());
			}
		}
		
		/*
		 * +1 HAN Iipeikou
		 */
		if(player.isIipeikou()){
			hanScore++;
			yakuList.add(Yaku.Iipeikou.toString());
		}
		
		/*
		 * +1 HAN Sanshoku Shoudoukou
		 */
		if(player.isSanshokuShoudoukou()){
			hanScore++;
			yakuList.add(Yaku.SanshokuShoudoukou.toString());
		}
		
		/*
		 * +2 HAN San Renkou
		 */
		if(player.isSanRenkou()){
			hanScore++;
			yakuList.add(Yaku.SanRenkou.toString());
		}
		
		/*
		 * +2 HAN Double Riichi
		 */
		if(player.isRiichi() && player.getThrownTiles().size() == 0 && player.getJustPickedTile() != null){
			hanScore++;
			yakuList.add(Yaku.DoubleRiichi.toString());
		}
		
		/*
		 * +2 HAN (open)/ +1 HAN (close) Sanshoku Doujin
		 */
		if(player.isSanshokuDoujin()){
			if(player.isPublic()){
				hanScore++;
			}
			else {
				hanScore+=2;
			}
			yakuList.add(Yaku.SanshokuDoujin.toString());
		}
		
		/*
		 * +2 HAN (open)/ +1 HAN (close) Sanshoku Doukou
		 */
		if(player.isSanshokuDoukou()){
			if(player.isPublic()){
				hanScore++;
			}
			else {
				hanScore+=2;
			}
			yakuList.add(Yaku.SanshokuDoukou.toString());
		}
		
		/*
		 * +2 HAN (open)/ +1 HAN (close) Ittsuu
		 */
		if(player.isIttsuu()){
			if(player.isPublic()){
				hanScore++;
			}
			else {
				hanScore+=2;
			}
			yakuList.add(Yaku.Ittsuu.toString());
		}
		
		/*
		 * +2 HAN (open)/ +1 HAN Chanta
		 */
		if(player.isChanta()){
			if(player.isPublic()){
				hanScore++;
			}
			else {
				hanScore+=2;
			}
			yakuList.add(Yaku.Chanta.toString());
		}
		
		/*
		 * +2 HAN Honroutou
		 */
		if(player.isHonroutou()){
			hanScore+=2;
			yakuList.add(Yaku.Honroutou.toString());
		}
		
		/*
		 * +2 HAN Toitoi
		 */
		if(player.isToitoi()){
			hanScore+=2;
			yakuList.add(Yaku.Toitoi.toString());
		}
		
		/*
		 * +2 HAN San Ankou
		 */
		if(player.isSanAnkou()){
			hanScore+=2;
			yakuList.add(Yaku.SanAnkou.toString());
		}
		
		/*
		 * +2 HAN San Kantsu
		 */
		if(player.isSanKantsu()){
			hanScore+=2;
			yakuList.add(Yaku.SanKantsu.toString());
		}
		
		/*
		 * +2 HAN Chiitoitsu
		 */
		if(player.isChiiToitsu()){
			hanScore+=2;
			yakuList.add(Yaku.ChiiToitsu.toString());
		}
		
		/*
		 * +2 HAN Shou Sangen
		 */
		if(player.isShouSangen()){
			hanScore+=2;
			yakuList.add(Yaku.ShouSangen.toString());
		}
		
		/*
		 * +3 HAN (close)/ +2 HAN (open) Honitsu
		 */
		if(player.isHonichi()){
			if(player.isPublic()){
				hanScore += 2;
			}
			else {
				hanScore += 3;
			}
			yakuList.add(Yaku.Honichi.toString());
		}
		
		/*
		 * +3 HAN (close)/ +2 HAN (open) Junchan
		 */
		if(player.isJunchan()){
			if(player.isPublic()){
				hanScore += 2;
			}
			else {
				hanScore += 3;
			}
			yakuList.add(Yaku.Junchan.toString());
		}
		
		/*
		 * +3 HAN Ryanpeikou
		 */
		if(player.isRyanpeikou()){
			hanScore += 3;
			yakuList.add(Yaku.Ryanpeikou.toString());
		}
		
		/*
		 * +6 HAN (close) / +5 HAN (open) Chinitsu
		 */
		if(player.isChinitsu()){
			if(player.isPublic()){
				hanScore += 6;
			}
			else{
				hanScore += 5;
			}
			yakuList.add(Yaku.Chinitsu.toString());
		}
		
		/*
		 * + 5 HAN Nagashi Mangan
		 */
		if(player.isNagashiMangan()){
			hanScore += 5;
			yakuList.add(Yaku.NagashiMangan.toString());
		}
		
		/*
		 * YAKUMAN
		 */
		int yakumanHanScore = 0;
		
		/*
		 * +13/26 HAN Kokushi Musou 
		 */
		if(player.isKokushiMusou13Han()){
			yakumanHanScore += 13;
			yakumanList.add(Yakuman.KokushiMusou13Han.toString());
		}		
		else if(player.isKokushiMusou26Han()){
			yakumanHanScore += 26;
			yakumanList.add(Yakuman.KokushiMusou26Han.toString());
		}
		
		/*
		 * +13/26 HAN Suu Ankou / Suu Ankou Tanki
		 */
		if(player.isSuuAnkou13Han()){
			yakumanHanScore += 13;
			yakumanList.add(Yakuman.SuuAnkou13Han.toString());
		}
		else if(player.isSuuAnkou26Han()){
			yakumanHanScore += 26;
			yakumanList.add(Yakuman.SuuAnkou26Han.toString());
		}
		
		/*
		 * +13 HAN Dai Sangen 
		 */
		if(player.isDaiSangen13Han()){
			yakumanHanScore += 13;
			yakumanList.add(Yakuman.DaiSangen13Han.toString());
		}
		
		/*
		 * +13 HAN Shou Suushii
		 */
		if(player.isShouSuushii13Han()){
			yakumanHanScore += 13;
			yakumanList.add(Yakuman.ShouSuushii13Han.toString());
		}
		
		/*
		 * +26 HAN Dai Suushii 
		 */
		if(player.isDaiSuushii26Han()){
			yakumanHanScore += 13;
			yakumanList.add(Yakuman.DaiSuushii26Han.toString());
		}
		
		/*
		 * +13 HAN Ryuu Iisou
		 */
		if(player.isRyuuIisou13Han()){
			yakumanHanScore += 13;
			yakumanList.add(Yakuman.RyuuIisou13Han.toString());
		}
		
		/*
		 * +13 HAN Tsuu Iisou
		 */
		if(player.isTsuuIisou13Han()){
			yakumanHanScore += 13;
			yakumanList.add(Yakuman.TsuuIisou13Han.toString());
		}
		
		/*
		 * +26 HAN Dai Chisei
		 */
		if(player.isDaiChisei26Han()){
			yakumanHanScore += 26;
			yakumanList.add(Yakuman.DaiChisei26Han.toString());
		}
		
		/*
		 * +13 HAN Chinroutou
		 */
		if(player.isChinroutou13Han()){
			yakumanHanScore += 13;
			yakumanList.add(Yakuman.Chinroutou13Han.toString());
		}
		
		/*
		 * +13/26 HAN Chuuren Poutou		
		 */
		if(player.isChuurenPoutou13Han()){
			yakumanHanScore += 13;
			yakumanList.add(Yakuman.ChuurenPoutou13Han.toString());
		}
		else if(player.isChuurenPoutou26Han()){
			yakumanHanScore += 26;
			yakumanList.add(Yakuman.ChuurenPoutou26Han.toString());
		}
		
		/*
		 * +13 HAN Dai Sharin (Pin) /Dai Chikurin (Sou) /Dai Suurin (Man)
		 */
		if(player.isDaiSharin13Han()){
			yakumanHanScore += 13;
			yakumanList.add(Yakuman.DaiSharin13Han.toString());
		}
		
		/*
		 * +13 HAN Suu Renkou
		 */
		if(player.isSuuRenkou13Han()){
			yakumanHanScore += 13;
			yakumanList.add(Yakuman.SuuRenkou13Han.toString());
		}
		
		/*
		 * +13 HAN Suu Kantsu
		 */
		if(player.isSuuKantsu13Han()){
			yakumanHanScore += 13;
			yakumanList.add(Yakuman.SuuKantsu13Han.toString());
		}
	
		
		/*
		 * OTHER YAKUMAN
		 */
		int otherYakumanHanScore = 0;
		
		/*
		 * +13 HAN Kazoe-Yakuman
		 */
		if(hanScore >= 13){
			otherYakumanHanScore += 13;
			otherYakumanList.add(Yakuman.KazoeYakuman.toString());
		}
		
		/*
		 * +13 HAN Tenhou
		 */
		if(player.getPlayerPosition() == 0 && player.getThrownTiles().size() == 0 && player.getJustPickedTile() != null){
			otherYakumanHanScore += 13;		
			otherYakumanList.add(Yakuman.Tenhou.toString());
		}
		
		/*
		 * +13 HAN Chiihou
		 */
		if(player.getPlayerPosition() != 0 && player.getThrownTiles().size() == 0 && player.getJustPickedTile() != null){
			otherYakumanHanScore += 13;
			otherYakumanList.add(Yakuman.Chiihou.toString());
		}
		
		/*
		 * +13 HAN Renhou
		 */
		if(player.getPlayerPosition() != 0 && player.getJustPickedTile() == null){
			otherYakumanHanScore += 13;
			otherYakumanList.add(Yakuman.Renhou.toString());
		}
		
		/*
		 * +13 HAN Renchan. Ignore it.
		 */
		
		/*
		 * +13 Shiisanpuutaa / Shiisanbudou
		 */
		if(player.isShiisanpuutaa() && !player.isPublic()){
			int counter1 = 0;			
			int counter2 = 0;
			
			/*
			 * Only round 1
			 */
			for(Iterator<Player> iPlayers = this.getPlayers().iterator(); iPlayers.hasNext();){
				Player iPlayer = iPlayers.next();
				
				if(iPlayer.getThrownTiles().size() == 1){
					counter1++;
				}
				
				if(iPlayer.getRightTiles().size() == 0){
					counter2++;
				}
			}
			
			if(counter1 == 4 && counter2 == 4){
				otherYakumanHanScore += 13;
				otherYakumanList.add(Yakuman.Shiisanpuutaa.toString());
			}
		}
		
		/*
		 * +13 HAN Shiisuupuutaa
		 */
		if(player.isShiisuupuutaa() && !player.isPublic()){
			int counter1 = 0;			
			int counter2 = 0;
			
			/*
			 * Only round 1
			 */
			for(Iterator<Player> iPlayers = this.getPlayers().iterator(); iPlayers.hasNext();){
				Player iPlayer = iPlayers.next();
				
				if(iPlayer.getThrownTiles().size() == 1){
					counter1++;
				}
				
				if(iPlayer.getRightTiles().size() == 0){
					counter2++;
				}
			}
			
			if(counter1 == 4 && counter2 == 4){
				otherYakumanHanScore += 13;
				otherYakumanList.add(Yakuman.Shiisuupuutaa.toString());
			}
		}		
		
		player.setHanScore(hanScore + yakumanHanScore + otherYakumanHanScore);
	}
	
	public synchronized void calculateFuScore(Player player){
		
		int fuScore = 0;
		
		/*
		 * Init: 25 Fu for Yaku Chitoitsu, ignore the other 
		 */
		if(player.getPairSet().size() == 7){
			fuScore = 25;
		}
		else {
			/*
			 * Init Fu
			 */
			if(!player.isPublic() && player.isRon()){
				fuScore = 30;
			}
			else {
				fuScore = 20;
			}
			
			/*
			 * Increase Fu for Pon and Kan
			 */
		
			for(Iterator<Tile> iTiles = player.getPonSet().iterator(); iTiles.hasNext();){
				Tile iTile = iTiles.next();
				if(player.isPublic()){
					if(iTile.isYaochuhai()){
						fuScore = fuScore + 4;
					}
					else {
						fuScore = fuScore + 2;
					}
				}
				else {
					if(iTile.isYaochuhai()){
						fuScore = fuScore + 8;
					}
					else {
						fuScore = fuScore + 4;
					}
				}
			}
			
			for(Iterator<Tile> iTiles = player.getKanSet().iterator(); iTiles.hasNext();){
				Tile iTile = iTiles.next();
				if(player.isPublic()){
					if(iTile.isYaochuhai()){
						fuScore = fuScore + 16;
					}
					else {
						fuScore = fuScore + 8;
					}
				}	
				else {
					if(iTile.isYaochuhai()){
						fuScore = fuScore + 32;
					}
					else {
						fuScore = fuScore + 16;
					}
				}
			}				
			
			
			/*
			 * Increade Fu for Pair
			 */
			
			
			/*
			 * Increate Fu for Tenpai
			 */
			
			if(player.isKanchan() || player.isPenchan() || player.isTanki()){
				fuScore = fuScore + 2;
			}
			
			if(fuScore == 0){
				fuScore = fuScore + 0;
			}
			else {
				fuScore = fuScore + 2;
			}
			
			if(player.isPublic() && fuScore == 20){
				fuScore = fuScore + 10;
			}
			
			if(fuScore % 10 != 0){
				fuScore = ((fuScore / 10) + 1) * 10;
			}			
		}
		
		player.setFuScore(fuScore);
	}
	
	public synchronized void calculateScore(Player player){
		double score = player.getFuScore() * Math.pow(2, 2 + player.getHanScore());		
		int hanScore = player.getHanScore();		
			
		/*
		 * Need to check Yakyman also
		 */		
		if(score > 2000){
			if(hanScore <= 5){
				score = 2000;
			}
			else if(hanScore > 5 && hanScore < 8){
				score = 3000;
			}
			else if(hanScore > 7 && hanScore < 11){
				score = 4000;
			}
			else if(hanScore > 10 && hanScore < 13){
				score = 6000;
			}
			else {
				score = 8000;
			}
		}
		
		player.setScore(score);
	}
	
	public boolean is4Gio(){
		
		boolean flag = true;		
		
		for(Iterator<Player> iPlayers = this.getPlayers().iterator(); iPlayers.hasNext();){
			Player iPlayer = iPlayers.next();
			
			if(iPlayer.getThrownTiles().size() != 1){
				flag = false;
				break;
			}
		}
		
		if(flag) {
			
			Tile firstTile = this.getPlayers().get(0).getThrownTiles().get(0);
			
			if(!firstTile.isRong()){
				return false;
			}
			
			for(Iterator<Player> iPlayers = this.getPlayers().iterator(); iPlayers.hasNext();){
				Player iPlayer = iPlayers.next();
				if(iPlayer.getThrownTiles().get(0).getType() != firstTile.getType()){
					return false;
				}
			}
			
			return true;
		}
		else {
			return false;
		}			
	}
	
	public boolean is4Riichi(){
		boolean flag = true;
		
		for(Iterator<Player> iPlayers = this.getPlayers().iterator(); iPlayers.hasNext();){
			Player iPlayer = iPlayers.next();
			if(!iPlayer.isRiichiWaiting()){
				return false;
			}
		}
		
		return flag;
	}
	
	public void resetTimers(){
		for(Iterator<Player> iPlayers = players.iterator(); iPlayers.hasNext();){
			Player iPlayer = (Player)iPlayers.next();			
			iPlayer.cancelTimer();
		}
	}
		
	public synchronized Player getThrownPlayer(){
		return this.thrownPlayer;		
	}

	public int getDice1() {
		return dice1;
	}

	public void setDice1(int dice1) {
		this.dice1 = dice1;
	}

	public int getDice2() {
		return dice2;
	}

	public void setDice2(int dice2) {
		this.dice2 = dice2;
	}

	public int getFirstDoraTileId() {
		return firstDoraTileId;
	}

	public void setFirstDoraTileId(int firstDoraTileId) {
		this.firstDoraTileId = firstDoraTileId;
	}

	public Player getPointingPlayer() {
		return pointingPlayer;
	}

	public void setPointingPlayer(Player pointingPlayer) {
		this.pointingPlayer = pointingPlayer;
	}

	public int getNumRonPlayer() {
		return numRonPlayer;
	}

	public void setNumRonPlayer(int numRonPlayer) {
		this.numRonPlayer = numRonPlayer;
	}

	public List<Player> getCanCallRonPlayers() {
		return canCallRonPlayers;
	}

	public void setCanCallRonPlayers(List<Player> canCallRonPlayers) {
		this.canCallRonPlayers = canCallRonPlayers;
	}

	public void setThrownPlayer(Player thrownPlayer) {
		this.thrownPlayer = thrownPlayer;
	}

	public int getNumTileOnWall() {
		return numTileOnWall;
	}

	public void setNumTileOnWall(int numTileOnWall) {
		this.numTileOnWall = numTileOnWall;
	}

	public Map<Integer, Boolean> getNumKCPCallDuringRiichiMap() {
		return numKCPCallDuringRiichiMap;
	}

	public void setNumKCPCallDuringRiichiMap(
			Map<Integer, Boolean> numKCPCallDuringRiichiMap) {
		this.numKCPCallDuringRiichiMap = numKCPCallDuringRiichiMap;
	}

	public Player getPlayerInTurn() {
		return playerInTurn;
	}

	public void setPlayerInTurn(Player playerInTurn) {
		this.playerInTurn = playerInTurn;
	}

	public int getCounter() {
		return counter;
	}

	public void setCounter(int counter) {
		this.counter = counter;
	}

	public void setDoras(List<Tile> doras) {
		this.doras = doras;
	}

	public List<Tile> getPickedDeathTile() {
		return pickedDeathTile;
	}

	public void setPickedDeathTile(List<Tile> pickedDeathTile) {
		this.pickedDeathTile = pickedDeathTile;
	}

	public boolean isHasCalledFinishAPI() {
		return hasCalledFinishAPI;
	}

	public void setHasCalledFinishAPI(boolean hasCalledFinishAPI) {
		this.hasCalledFinishAPI = hasCalledFinishAPI;
	}

	public synchronized int getCounterTimer() {
		return counterTimer;
	}

	public synchronized void setCounterTimer(int counterTimer) {
		this.counterTimer = counterTimer;
	}
		
	public boolean isHasPlayerCalledRon() {
		return hasPlayerCalledRon;
	}

	public void setHasPlayerCalledRon(boolean hasPlayerCalledRon) {
		this.hasPlayerCalledRon = hasPlayerCalledRon;
	}

	public List<Player> getCalledRiichiPlayers() {
		return calledRiichiPlayers;
	}

	public void setCalledRiichiPlayers(List<Player> calledRiichiPlayers) {
		this.calledRiichiPlayers = calledRiichiPlayers;
	}

	public List<Player> getCalledRonPlayers() {
		return calledRonPlayers;
	}

	public void setCalledRonPlayers(List<Player> calledRonPlayers) {
		this.calledRonPlayers = calledRonPlayers;
	}

	public List<Player> getTenpaiPlayers() {
		return tenpaiPlayers;
	}

	public void setTenpaiPlayers(List<Player> tenpaiPlayers) {
		this.tenpaiPlayers = tenpaiPlayers;
	}

	public List<Tile> getThrownTiles() {
		return thrownTiles;
	}

	public void setThrownTiles(List<Tile> thrownTiles) {
		this.thrownTiles = thrownTiles;
	}

	public String getSkipType() {
		return skipType;
	}

	public void setSkipType(String skipType) {
		this.skipType = skipType;
	}

	public List<Player> getCalledKanPlayers() {
		return calledKanPlayers;
	}

	public void setCalledKanPlayers(List<Player> calledKanPlayers) {
		this.calledKanPlayers = calledKanPlayers;
	}

	public Map getMahjongCache() {
		return mahjongCache;
	}

	public void setMahjongCache(Map mahjongCache) {
		this.mahjongCache = mahjongCache;
	}

	public Player getCallingChiPlayer() {
		return calingChiPlayer;
	}

	public void setCallingChiPlayer(Player callingChiPlayer) {
		this.calingChiPlayer = callingChiPlayer;
	}	
	
	public Player getCallingPonPlayer() {
		return callingPonPlayer;
	}

	public void setCallingPonPlayer(Player callingPonPlayer) {
		this.callingPonPlayer = callingPonPlayer;
	}

	public Player getCallingOpenKanPlayer() {
		return callingOpenKanPlayer;
	}

	public void setCallingOpenKanPlayer(Player callingOpenKanPlayer) {
		this.callingOpenKanPlayer = callingOpenKanPlayer;
	}

	public Player getCanCallChiPlayer() {
		return canCallChiPlayer;
	}

	public void setCanCallChiPlayer(Player canCallChiPlayer) {
		this.canCallChiPlayer = canCallChiPlayer;
	}

	public Player getCanCallPonPlayer() {
		return canCallPonPlayer;
	}

	public void setCanCallPonPlayer(Player canCallPonPlayer) {
		this.canCallPonPlayer = canCallPonPlayer;
	}

	public Player getCanCallOpenKanPlayer() {
		return canCallOpenKanPlayer;
	}

	public void setCanCallOpenKanPlayer(Player canCallOpenKanPlayer) {
		this.canCallOpenKanPlayer = canCallOpenKanPlayer;
	}
	
	public SFSObject makeResponse(ResponseCode code, SFSObject data)
	{
		SFSObject sfsObj = new SFSObject();
		sfsObj.putInt("returnCode", code.ordinal());
		if (data == null)
			data = new SFSObject();
		sfsObj.putSFSObject("data", data);		
		
		return sfsObj;
	}
	
	public SFSObject makeResponseMessage(String message)
	{
		SFSObject response = new SFSObject();
		response.putUtfString("message", message);
					
		return response;
	}
	
	public int rollDice(){
		Random rand = new Random();
		return rand.nextInt(6) + 1;		
	}
		
	public User getUserByPlayerId(int playerId, List<User> users){
		for(Iterator<User> iUsers = users.iterator(); iUsers.hasNext();){
			User iUser = iUsers.next();
			if(iUser.getId() == playerId){
				return iUser;
			}
		}
		return null;
	}

	public List<Tile> getDistinctTileByType(int tileType, List<Tile> tiles){
		
		Set<Tile> tileSet = new HashSet<Tile>();
		List<Tile> tileList = new ArrayList<Tile>();
		TileComparator comparator = new TileComparator();
		
		for (Iterator<Tile> iTiles = tiles.iterator(); iTiles.hasNext();){
			Tile iTile = iTiles.next();
			if(iTile.getType().ordinal() == tileType){
				tileSet.add(iTile);
			}
		}
		
		tileList.addAll(tileSet);
		Collections.sort(tileList, comparator);
		
		return tileList;
	}
	
	public List<Tile> getTileByType(int tileType, List<Tile> tiles){
				
		List<Tile> tileList = new ArrayList<Tile>();
		TileComparator comparator = new TileComparator();
		
		for (Iterator<Tile> iTiles = tiles.iterator(); iTiles.hasNext();){
			Tile iTile = iTiles.next();
			if(iTile.getType().ordinal() == tileType){
				tileList.add(iTile);
			}
		}
				
		Collections.sort(tileList, comparator);		
		return tileList;
	}
	
	public List<Integer> getTileIndexsByType(int tileType, List<Tile> tiles){						
		List<Integer> tileIndexs = new ArrayList<Integer>();
		
		for (Iterator<Tile> iTiles = tiles.iterator(); iTiles.hasNext();){
			Tile iTile = iTiles.next();
			if(iTile.getType().ordinal() == tileType){
				tileIndexs.add(iTile.getIndex());
			}
		}				
		Collections.sort(tileIndexs);
		
		return tileIndexs;
	}
	
	public List<Tile> getTilesByIndex(int tileIndex, List<Tile> tiles){
		List<Tile> subTiles = new ArrayList<Tile>();
		
		for(Iterator<Tile> iTiles = tiles.iterator(); iTiles.hasNext();){
			Tile iTile = iTiles.next();
			if(iTile.getIndex() == tileIndex){
				subTiles.add(iTile);
			}
		}
		
		return subTiles;
	}
	
	public void removeTileByType(int tileType, List<Tile> tiles){
		List<Tile> tilesTmp = new ArrayList<Tile>();
		tilesTmp.addAll(tiles);
		
		for (Iterator<Tile> iTiles = tilesTmp.iterator(); iTiles.hasNext();){
			Tile iTile = iTiles.next();
			if(iTile.getType().ordinal() == tileType){
				tiles.remove(iTile);
			}
		}
	}
		
	public List<Integer> tileArrayToTileIdArray(List<Tile> tiles){
		List<Integer> tileIds = new ArrayList<Integer>();
		
		for(Iterator<Tile> iTiles = tiles.iterator(); iTiles.hasNext();){
			Tile iTile = iTiles.next();
			tileIds.add(iTile.getId());
		}
		
		Collections.sort(tileIds);
		
		return tileIds;
	}
	
	public String tileIdsIntToString(List<Tile> tiles){
		StringBuffer str = new StringBuffer();
		
		for(Iterator<Tile> iTiles = tiles.iterator(); iTiles.hasNext();){
			str.append(iTiles.next().getId());
		}
		
		return str.toString();
	}
	
	public List<List<Integer>> tilesArrayToTileIdsArray(List<List<Tile>> tiles){
		List<List<Integer>> tileIdss = new ArrayList<List<Integer>>();
		
		for(Iterator<List<Tile>> iTiless = tiles.iterator(); iTiless.hasNext();){
			List<Tile> iTiles = iTiless.next();		
			tileIdss.add(tileArrayToTileIdArray(iTiles));
		}
		
		return tileIdss;
	}
		
	public ISFSArray javaArrayToSFSArray(List<Tile> tiles){
		ISFSArray sFSArray = new SFSArray();
		
		for(Iterator<Tile> iTiles = tiles.iterator(); iTiles.hasNext();){
			Tile iTile = iTiles.next();
			ISFSObject iSFSObject = iTile.toSFSObject();			
			sFSArray.addSFSObject(iSFSObject);
		}
		
		return sFSArray;
	}
	
	public ISFSArray javaArraysToSFSArray(List<List<Tile>> tiles){
		ISFSArray sFSArray = new SFSArray();
		
		for(Iterator<List<Tile>> iListTiles = tiles.iterator(); iListTiles.hasNext();){
			List<Tile> iListTile = iListTiles.next();			
			ISFSArray sSubFSArray = javaArrayToSFSArray(iListTile);
			sFSArray.addSFSArray(sSubFSArray);
		}
		
		return sFSArray;
	}
	
	public boolean isApiSuccess(SFSObject response)
	{
		if (response == null)
			return false;
		if (!response.containsKey("returnCode"))
			return false;		
		if (200 != Integer.parseInt(response.get("returnCode").getObject().toString()))
			return false;
		if (!response.containsKey("response"))
			return false;
		return true;
		
	}
	
	public String log(Room room, Player player, String handlerName, String content){
		StringBuffer str = new StringBuffer();
		str.append("Room: ")
			.append(room.getName())
			.append("-")
			.append(room.getId())			
			.append(" Player: ")
			.append(player.toString())
			.append(" ")
			.append(handlerName)
			.append(": ")
			.append(content);
		
		return str.toString();
	}
	
	public void callChi(MahjongExtension ext, List<User> users, Room room, Player player){		
		Map playerCache = player.getCache();		
		Response.Builder chiResponse = new Response.Builder();
		chiResponse.bonusTime(Integer.valueOf(String.valueOf(playerCache.get(MObject.bonusTime.toString()))))
					.chiTiles((Collection<Integer>)playerCache.get(MObject.chiTiles.toString()));
		
		playerCache.remove(MObject.bonusTime.toString());
		playerCache.remove(MObject.chiTiles.toString());
				
		User callingChiUser = getUserByPlayerId(player.getPlayerId(), users);
		
		CallChiHandler callChiHandler = new CallChiHandler();
		callChiHandler.callChi(ext, callingChiUser, chiResponse.build().getSFSObject());			
	}
	
	public SFSArray getUsersAmount(){
		List<User> users = room.getUserList();
		double roomBet = room.getVariable(RoomVarName.roomBet.toString()).getDoubleValue();
		double initScore = room.getVariable(RoomVarName.init_score.toString()).getDoubleValue();
		SFSArray userAmounts = new SFSArray();
		
		for(int i = 0; i < users.size(); i++){
			User iUser = users.get(i);
			Response.Builder userAmount = new Response.Builder();					
			Player iPlayer = this.getPlayerById(iUser.getId());
			userAmount.user_id(iUser.getName());					
							
			double amount = (iPlayer.getExtendedScore() * roomBet) / initScore;
											
			userAmount.amount(amount);						
			userAmounts.addSFSObject(userAmount.build().getSFSObject());
		}
		
		return userAmounts;
	}
	
	public SFSObject gameFinishGame(Player player, String gameActionType){		
		SFSArray userAmounts = getUsersAmount();
		logger.info(this.log(room, player, gameActionType, "userAmounts: " + userAmounts));
		
		String access_token = room.getVariable(RoomVarName.access_token.toString()).getStringValue();
		String platform_room_id = room.getVariable(RoomVarName.room_id.toString()).getStringValue();
		String platform_transaction_id = room.getVariable(RoomVarName.transaction_id.toString()).getStringValue();
					
		logger.info(this.log(room, player, gameActionType, "Start game_finish_game"));
		
		SFSObject sfsResponse = game_finish_game
				(access_token, platform_room_id, platform_transaction_id, userAmounts);
		
		if (!this.isApiSuccess(sfsResponse))
		{
			String msg = gameActionType + ": Failed in game_finish_game API: " + sfsResponse.get("msgText").getObject().toString();
			
			SFSObject res = this.makeResponseMessage(msg);															
			logger.info(this.log(room, player, GameActionType.callTsumo.toString(), msg));
			
			return res;
		}
		
		logger.info(this.log(room, player, gameActionType, "End game_finish_game"));
		return null;
	}
	
	public SFSObject useGetBalance(Response.Builder sfsUser, User user, Player player, String gameActionType){
		logger.info(this.log(room, player, gameActionType, "Start user_get_balance API"));
		SFSObject sfsBalance = PlatformClient.user_get_balance(user.getVariable(UserVarName.access_token.toString()).getStringValue());
		
		if (this.isApiSuccess(sfsBalance)){
			String balanceText = sfsBalance.getSFSObject("response").get("balance").getObject().toString();					
			double newBalance = Double.parseDouble(balanceText);
			logger.info(this.log(room, player, gameActionType, "newBalance: " + newBalance));
			double oldBalance = user.getVariable(UserVarName.balance.toString()).getDoubleValue();
			logger.info(this.log(room, player, gameActionType, "oldBalance: " + oldBalance));
			sfsUser.money(newBalance - oldBalance);							
		}
		else {
			String msg = gameActionType + ": Failed in user_get_balance API : " + sfsBalance.get("msgText").getObject().toString();							
			SFSObject res = this.makeResponseMessage(msg);			
			logger.info(this.log(room, player, gameActionType, msg));					
			return res;
		}
		
		logger.info(this.log(room, player, gameActionType, "End user_get_balance API"));
		
		return null;
	}
	
	public SFSObject gamePlayerJoin(User user, String gameActionType){
		String access_token = user.getVariable(UserVarName.access_token.toString()).getStringValue();
		String platform_room_id = room.getVariable(RoomVarName.room_id.toString()).getStringValue();
		
		logger.info(gameActionType + ": start game_player_join");
		logger.info(gameActionType + ": access_token: " + access_token);
		logger.info(gameActionType + ": platform_room_id: " + platform_room_id);			
		
		SFSObject sfsResponse = game_player_join(access_token, platform_room_id);
		logger.info(gameActionType + ": sfsResponse: " + sfsResponse);
		if (!Common.isApiSuccess(sfsResponse)) {
			String msg = gameActionType + ": Failed in game_player_join API : " + sfsResponse.get("msgText").getObject().toString();			
			SFSObject response = this.makeResponseMessage(msg);			
			return response;			
		}
		
		logger.info(gameActionType + ": end game_player_join");
		
		return null;
	}
	
	public SFSObject gamePlayerLeave(User user, String gameActionType){
		String access_token = user.getVariable(UserVarName.access_token.toString()).getStringValue();
		String platform_room_id = room.getVariable(RoomVarName.room_id.toString()).getStringValue();
		Player player = this.getPlayerById(user.getId());
		
		logger.info(gameActionType + ": start game_player_leave");
		
		SFSObject sfsResponse = game_player_leave(access_token, platform_room_id);
		
		if (!this.isApiSuccess(sfsResponse))
		{
			String msg = gameActionType + ": Failed in game_player_leave API: " + sfsResponse.get("msgText").getObject().toString();									
			logger.info(this.log(room, player, gameActionType, msg));
			SFSObject response = this.makeResponseMessage(msg);	
			return response;
		}
		
		logger.info(gameActionType + ": end game_player_leave");
		
		return null;
	}
	
	public void resetTileSet(){
		setCanCallChiPlayer(null);
		setCanCallPonPlayer(null);
		setCanCallOpenKanPlayer(null);		
	}
	
	/**
	 * @param access_token
	 * @param platform_room_id
	 * @return
	 */
	public SFSObject game_player_leave(String access_token, String platform_room_id)
	{
		SFSObject response = null;
		if(!isDebug){
			String uri = String.format("/game/player_leave/%s",platform_room_id);
			String query = String.format("?access_token=%s", access_token);
			response = sendHttpRequest("GET", uri+query, "");
		}
		else {
			response = getSFSObject();
			SFSObject data = new SFSObject();
			data.putBool("status", true);
			response.putSFSObject("response", data);
		}
		
		return response;
	}
	
	public SFSObject game_player_join(String access_token, String platform_room_id)
	{
		SFSObject response = null;
		if(!isDebug){
			String uri = String.format("/game/player_join/%s",platform_room_id);
			String query = String.format("?access_token=%s", access_token);
			response = sendHttpRequest("GET", uri+query, "");
		}
		else {
			response = getSFSObject();
			SFSObject data = new SFSObject();
			data.putBool("status", true);
			response.putSFSObject("response", data);
		}
		
		return response;
	}
	
	/***	 
	 * @param access_token
	 * @param room_id
	 * @param transaction_id
	 * @param transactionResult
	 * @return
	 */
	public SFSObject game_finish_game(String access_token, String room_id, String transaction_id, SFSArray transactionResult)
	{
		SFSObject response = null;
		
		if(!isDebug){
			String uri = "/game/finish_game2";
			String query = String.format("?access_token=%s", access_token);
			
			SFSObject body = new SFSObject();
			body.putUtfString("room_id", room_id);
			body.putUtfString("transaction_id", transaction_id);
			body.putSFSArray("result", transactionResult);
			
			response = sendHttpRequest("POST", uri+query, body.toJson());
		}
		else {
			response = getSFSObject();
			SFSObject data = new SFSObject();
			data.putBool("status", true);
			
			SFSObject balance = new SFSObject();
			balance.putInt("", 10000);
			
			data.putSFSObject("balance", balance);
			
			response.putSFSObject("response", new SFSObject());
		}
		
		return response;
	}
	
	public SFSObject getSFSObject() {
		SFSObject sFSObject = new SFSObject();		
		sFSObject.putBool("status", true);
		sFSObject.putInt("returnCode", 200);
		sFSObject.putUtfString("dateTime", String.valueOf(Calendar.getInstance().getTime()));
		
		return sFSObject;
	}
	
	public SFSObject sendHttpRequest(String method, String uri, String postData) {

		String url = room.getVariable(PlatformConfig.PLATFORM_URL).getStringValue() + uri;
		logger.info("url: " + url);
		String responseJson = "";
		try {
			URL obj = new URL(url);
			HttpURLConnection con = (HttpURLConnection) obj.openConnection();

			// optional default is GET
			con.setRequestMethod(method);
				
			// add request header
			con.setRequestProperty("User-Agent", "SFS2X");
			con.setRequestProperty("Content-Type", "application/json");
			con.setRequestProperty("Accept-Charset", "UTF-8");

			String xGameId = room.getVariable(PlatformConfig.X_GAME_ID).getStringValue();
			String xSecretKey = room.getVariable(PlatformConfig.X_SECRET_KEY).getStringValue();
			
			logger.info("xGameId: " + xGameId);
			logger.info("xSecretKey: " + xSecretKey);
			
			con.setRequestProperty("x-game-id", xGameId);
			con.setRequestProperty("x-secret-key", xSecretKey);
			
			if ( method.equals("POST")){
				// Send post request
				con.setDoOutput(true);
				DataOutputStream wr = new DataOutputStream(con.getOutputStream());
				wr.write(postData.getBytes("UTF-8"));
				wr.flush();
				wr.close();
			}

			int responseCode = con.getResponseCode();
			
			//trace("Sending '"+method+"' request to URL : " + url);
			//if (postData != null && postData.length() > 0)
				//log.info("Post Data: " + postData);
			//log.info("Response Code : " + responseCode);

			BufferedReader in = new BufferedReader(new InputStreamReader(
					con.getInputStream()));
			String inputLine;
			StringBuffer response = new StringBuffer();

			while ((inputLine = in.readLine()) != null) {
				response.append(inputLine);
			}
			in.close();

			//log.info("RESPONSE: " + response.toString());
			
			responseJson = response.toString();
			
		} catch (Throwable t) 
		{
			//log.error("Failed in sendHttpRequest : ", t);
		}
		
		// print result
		if (responseJson == ""){
			return null;
		}
		SFSObject sfsResponse = (SFSObject) SFSObject.newFromJsonData(responseJson);		
		return sfsResponse;
	}
	
	public void setIsDebug(boolean isDebug){
		this.isDebug = isDebug;
	}
		
	public Logger getLogger() {
		return logger;
	}

	public void setLogger(Logger logger) {
		this.logger = logger;
	}

	public Room getRoom() {
		return room;
	}

	public void setRoom(Room room) {
		this.room = room;
	}

	public Player getCallingPickPlayer() {
		return callingPickPlayer;
	}

	public void setCallingPickPlayer(Player callingPickPlayer) {
		this.callingPickPlayer = callingPickPlayer;
	}

	public boolean isHasPickedTile() {
		return hasPickedTile;
	}

	public void setHasPickedTile(boolean hasPickedTile) {
		this.hasPickedTile = hasPickedTile;
	}
}